// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package types

import (
	"fmt"
	"go/ast"
	"go/constant"
	"go/internal/typeparams"
	"go/token"
	"sort"
	"strconv"
	"strings"
	"unicode"
)

// declInfo描述包级别的常量、类型、变量或func声明。
type declInfo struct {
	file      *Scope        // 包含此声明的文件范围
	lhs       []*Var        // n:1变量声明的lhs，或nil 
	vtyp      ast.Expr      // 类型，或nil（仅适用于常量和变量声明）
	init      ast.Expr      // init/orig表达式，或nil（仅适用于常量和变量声明）
	inherited bool          // 如果设置，init表达式继承自上一个常量声明
	tdecl     *ast.TypeSpec // 类型声明，或nil 
	fdecl     *ast.FuncDecl // func声明，或nil 

	// deps字段跟踪初始化表达式依赖项。
	deps map[Object]bool // 延迟初始化
}

// hasInitializer报告声明的对象是否具有初始化
// 表达式或函数体。
func (d *declInfo) hasInitializer() bool {
	return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil
}

// addDep将obj添加到d的初始化表达式所依赖的对象集中。
func (d *declInfo) addDep(obj Object) {
	m := d.deps
	if m == nil {
		m = make(map[Object]bool)
		d.deps = m
	}
	m[obj] = true
}

// 算术匹配检查常量或变量decl的lhs和rhs是否具有适当数量的名称和初始表达式。对于const 
// decls，init是提供init expr的值规范；对于
// var decls，init为nil（在本例中，init表达式为s）。
func (check *Checker) arityMatch(s, init *ast.ValueSpec) {
	l := len(s.Names)
	r := len(s.Values)
	if init != nil {
		r = len(init.Values)
	}

	const code = _WrongAssignCount
	switch {
	case init == nil && r == 0:
		// var decl w/o init expr 
		if s.Type == nil {
			check.errorf(s, code, "missing type or init expr")
		}
	case l < r:
		if l < len(s.Values) {
			// init exprs from s 
			n := s.Values[l]
			check.errorf(n, code, "extra init expr %s", n)
			// TODO（gri）avoid declated but not used error here 
		} else {
			// init exprs“inherited”
			check.errorf(s, code, "extra init expr at %s", check.fset.Position(init.Pos()))
			// TODO（gri）avoid declated but used error here 
		}
	case l > r && (init != nil || r != 1):
		n := s.Names[r]
		check.errorf(n, code, "missing init expr for %s", n)
	}
}

func validatedImportPath(path string) (string, error) {
	s, err := strconv.Unquote(path)
	if err != nil {
		return "", err
	}
	if s == "" {
		return "", fmt.Errorf("empty string")
	}
	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
	for _, r := range s {
		if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
			return s, fmt.Errorf("invalid character %#U", r)
		}
	}
	return s, nil
}

// declarePkgObj在包范围内声明obj，记录其标识->obj映射，
// 和更新检查。objMap。对象不能是函数或方法。
func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) {
	assert(ident.Name == obj.Name())

	// 规范：“名为init 
	// 的包作用域或文件作用域标识符只能声明为具有此（func（））签名的函数。”
	if ident.Name == "init" {
		check.errorf(ident, _InvalidInitDecl, "cannot declare init - must be func")
		return
	}

	// spec:“主包必须有包名main，并声明
	// 一个不带参数且不返回值的函数main。”
	if ident.Name == "main" && check.pkg.name == "main" {
		check.errorf(ident, _InvalidMainDecl, "cannot declare main - must be func")
		return
	}

	check.declare(check.pkg.scope, ident, obj, token.NoPos)
	check.objMap[obj] = d
	obj.setOrder(uint32(len(check.objMap)))
}

// filename返回适合调试输出的文件名。
func (check *Checker) filename(fileNo int) string {
	file := check.files[fileNo]
	if pos := file.Pos(); pos.IsValid() {
		return check.fset.File(pos).Name()
	}
	return fmt.Sprintf("file[%d]", fileNo)
}

func (check *Checker) importPackage(at positioner, path, dir string) *Package {
	// 如果我们已经为给定的（路径，目录）
	// 对准备了一个包，请使用它，而不是进行完整导入。
	// 检查程序。impMap仅缓存标记为Complete 
	// 或fake（失败导入的虚拟包）的包。不完整但
	// 非假冒包装确实需要进口才能完成。
	key := importKey{path, dir}
	imp := check.impMap[key]
	if imp != nil {
		return imp
	}

	// 还没有包=>导入它
	if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) {
		imp = NewPackage("C", "C")
		imp.fake = true // 包范围未填充
		imp.cgo = check.conf.go115UsesCgo
	} else {
		// 普通导入
		var err error
		if importer := check.conf.Importer; importer == nil {
			err = fmt.Errorf("Config.Importer not installed")
		} else if importerFrom, ok := importer.(ImporterFrom); ok {
			imp, err = importerFrom.ImportFrom(path, dir, 0)
			if imp == nil && err == nil {
				err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir)
			}
		} else {
			imp, err = importer.Import(path)
			if imp == nil && err == nil {
				err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
			}
		}
		// 确保我们有一个有效的包名
		// （这里的错误只能通过创建后的包操作发生）
		if err == nil && imp != nil && (imp.name == "_" || imp.name == "") {
			err = fmt.Errorf("invalid package name: %q", imp.name)
			imp = nil // 在
		}
		if err != nil {
			check.errorf(at, _BrokenImport, "could not import %s (%s)", path, err)
			if imp == nil {
				// 创建一个新的假包
				// 想出一个合理的包名（启发式）
				name := path
				if i := len(name); i > 0 && name[i-1] == '/' {
					name = name[:i-1]
				}
				if i := strings.LastIndex(name, "/"); i >= 0 {
					name = name[i+1:]
				}
				imp = NewPackage(path, name)
			}
			// 继续尽我们所能使用这个包
			imp.fake = true // 避免后续查找失败
		}
	}

	// 包应该是完整的或标记为假的，但要小心
	if imp.complete || imp.fake {
		check.impMap[key] = imp
		// 一旦我们格式化了一条错误消息，保持pkgPathMap 
		// 后续导入的最新信息。
		if check.pkgPathMap != nil {
			check.markImports(imp)
		}
		return imp
	}

	// 出现问题（导入程序可能返回了不完整的包，没有错误）
	return nil
}

// collectObjects收集所有文件和包对象，并将它们插入各自的作用域中。它还执行导入，并将
// 方法与接收器基类型名称关联。
func (check *Checker) collectObjects() {
	pkg := check.pkg

	// pkgImports是迄今为止看到的任何包文件已经导入的包集。用于避免pkg中的重复条目。进口。分配并填充
	// 它（如果我们以增量方式检查测试文件，pkg.imports可能不是空的）。
	// 请注意，pkgImports由包（以及包路径）设置密钥，而不是由
	// importKey值设置密钥。两个不同的importKey值可能映射到同一个包
	// 这就是为什么我们不能使用检查。把地图放在这里。
	var pkgImports = make(map[*Package]bool)
	for _, imp := range pkg.imports {
		pkgImports[imp] = true
	}

	type methodInfo struct {
		obj  *Func      // 方法
		ptr  bool       // 如果指针接收器
		recv *ast.Ident // 接收器类型名称
	}
	var methods []methodInfo // 具有有效接收器和非空名称的收集方法
	var fileScopes []*Scope
	for fileNo, file := range check.files {
		// 包标识符表示当前包，
		// 但没有相应的包对象。
		check.recordDef(file.Name, nil)

		// 使用实际的源文件范围，而不是*ast。自
		// 后的文件范围不包括出现在文件开头或结尾的注释。
		// 保守一点，使用*ast。如果我们没有*标记，请提交扩展名。文件
		pos, end := file.Pos(), file.End()
		if f := check.fset.File(file.Pos()); f != nil {
			pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
		}
		fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
		fileScopes = append(fileScopes, fileScope)
		check.recordScope(file, fileScope)

		// 确定文件目录，解析导入所需的
		// 文件名可能为“”（通常用于测试），在这种情况下
		// 我们得到“”作为我们想要的目录。
		fileDir := dir(check.fset.Position(file.Name.Pos()).Filename)

		check.walkDecls(file.Decls, func(d decl) {
			switch d := d.(type) {
			case importDecl:
				// 导入包
				path, err := validatedImportPath(d.spec.Path.Value)
				if err != nil {
					check.errorf(d.spec.Path, _BadImportPath, "invalid import path (%s)", err)
					return
				}

				imp := check.importPackage(d.spec.Path, path, fileDir)
				if imp == nil {
					return
				}

				// 本地名称覆盖导入包名称
				name := imp.name
				if d.spec.Name != nil {
					name = d.spec.Name.Name
					if path == "C" {
						// 匹配cmd/compile（规范未规定）
						check.errorf(d.spec.Name, _ImportCRenamed, `cannot rename import "C"`)
						return
					}
				}

				if name == "init" {
					check.errorf(d.spec, _InvalidInitDecl, "cannot import package as init - init must be a func")
					return
				}

				// 将包添加到显式导入列表
				// （此功能为客户端提供方便
				// 不需要进行类型检查）
				if !pkgImports[imp] {
					pkgImports[imp] = true
					pkg.imports = append(pkg.imports, imp)
				}

				pkgName := NewPkgName(d.spec.Pos(), pkg, name, imp)
				if d.spec.Name != nil {
					// dot import，点代表包
					check.recordDef(d.spec.Name, pkgName)
				} else {
					check.recordImplicit(d.spec, pkgName)
				}

				if path == "C" {
					// match cmd/compile（规范未规定）
					pkgName.used = true
				}

				// add import to file scope 
				check.imports = append(check.imports, pkgName)
				if name == "." {
					// dot import 
					if check.dotImportMap == nil {
						check.dotImportMap = make(map[dotImportKey]*PkgName)
					}
					// merge imported scope with file scope 
					for name, obj := range imp.scope.elems {
						// 注意：此处避免急于解析（名称，obj），因此我们仅
						// 根据需要解析dot导入的对象。

						// 包作用域可能包含未导出的对象，
						// 不要导入它们！
						if token.IsExported(name) {
							// declare dot导入对象
							// （不要使用check.declare，因为它通过object.setScopePos修改对象
							// ，这会导致竞争条件；
							// 该对象可能同时导入多个文件范围
							// 。请参阅问题#32154。）
							if alt := fileScope.Lookup(name); alt != nil {
								check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", alt.Name())
								check.reportAltDecl(alt)
							} else {
								fileScope.insert(name, obj)
								check.dotImportMap[dotImportKey{fileScope, name}] = pkgName
							}
						}
					}
				} else {
					// 在文件作用域中声明导入的包对象
					// （由于我们之前调用了check.recordDef，因此无需提供s.Name）
					check.declare(fileScope, nil, pkgName, token.NoPos)
				}
			case constDecl:
				// 声明所有常量
				for i, name := range d.spec.Names {
					obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota)))

					var init ast.Expr
					if i < len(d.init) {
						init = d.init[i]
					}

					d := &declInfo{file: fileScope, vtyp: d.typ, init: init, inherited: d.inherited}
					check.declarePkgObj(name, obj, d)
				}

			case varDecl:
				lhs := make([]*Var, len(d.spec.Names))
				// 如果只有一个rhs初始值设定项，使用
				// 对所有lhs变量使用相同的declInfo d1 
				// 以便每个lhs变量依赖于相同的
				// rhs初始值设定项（n:1变量声明）。
				var d1 *declInfo
				if len(d.spec.Values) == 1 {
					// 仅在下面的for循环之后设置lhs元素，
					// 但这没关系，因为declareVar只在稍后阶段收集declInfo 
					// 。
					d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: d.spec.Type, init: d.spec.Values[0]}
				}

				// 声明所有变量
				for i, name := range d.spec.Names {
					obj := NewVar(name.Pos(), pkg, name.Name, nil)
					lhs[i] = obj

					di := d1
					if di == nil {
						// 单个赋值
						var init ast.Expr
						if i < len(d.spec.Values) {
							init = d.spec.Values[i]
						}
						di = &declInfo{file: fileScope, vtyp: d.spec.Type, init: init}
					}

					check.declarePkgObj(name, obj, di)
				}
			case typeDecl:
				if d.spec.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) {
					check.softErrorf(d.spec.TypeParams.List[0], _UnsupportedFeature, "type parameters require go1.18 or later")
				}
				obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil)
				check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, tdecl: d.spec})
			case funcDecl:
				name := d.decl.Name.Name
				obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil)
				hasTParamError := false // 避免重复类型参数错误
				if d.decl.Recv.NumFields() == 0 {
					// 常规函数
					if d.decl.Recv != nil {
						check.error(d.decl.Recv, _BadRecv, "method is missing receiver")
						// 将其视为函数
					}
					if name == "init" || (name == "main" && check.pkg.name == "main") {
						code := _InvalidInitDecl
						if name == "main" {
							code = _InvalidMainDecl
						}
						if d.decl.Type.TypeParams.NumFields() != 0 {
							check.softErrorf(d.decl.Type.TypeParams.List[0], code, "func %s must have no type parameters", name)
							hasTParamError = true
						}
						if t := d.decl.Type; t.Params.NumFields() != 0 || t.Results != nil {
							// TODO（rFindley）这应该是一个硬错误吗？
							check.softErrorf(d.decl, code, "func %s must have no arguments and no return values", name)
						}
					}
					if name == "init" {
						// 不要在包作用域中声明init函数-它们是不可见的
						obj.parent = pkg.scope
						check.recordDef(d.decl.Name, obj)
						// init函数必须有一个主体
						if d.decl.Body == nil {
							// TODO（gri）使此错误消息与上面的其他
							check.softErrorf(obj, _MissingInitBody, "missing function body")
						}
					} else {
						check.declare(pkg.scope, d.decl.Name, obj, token.NoPos)
					}
				} else {
					// 方法

					// TODO（rFindley）此代码的早期版本检查了
					// 方法没有类型参数，但这将在稍后
					// 类型检查函数类型时进行检查。确认
					// 我们不需要在这里检查tparams。

					ptr, recv, _ := check.unpackRecv(d.decl.Recv.List[0].Type, false)
					// （接收端无效的方法无法关联到类型，并且
					// 永远找不到名称为空的方法；不需要收集其中的任何
					// 方法。它们仍将与所有其他函数进行类型检查。）
					if recv != nil && name != "_" {
						methods = append(methods, methodInfo{obj, ptr, recv})
					}
					check.recordDef(d.decl.Name, obj)
				}
				if d.decl.Type.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError {
					check.softErrorf(d.decl.Type.TypeParams.List[0], _UnsupportedFeature, "type parameters require go1.18 or later")
				}
				info := &declInfo{file: fileScope, fdecl: d.decl}
				// 方法不是包级对象，但我们仍然在
				// 对象映射中跟踪它们，以便我们可以像处理常规函数一样处理它们（如果
				// 接收器无效）；在将
				// 与下面的接收机基本类型关联时，我们还需要它们的fdecl信息。
				check.objMap[obj] = info
				obj.setOrder(uint32(len(check.objMap)))
			}
		})
	}

	// 验证包和文件作用域中的对象具有不同的名称
	for _, scope := range fileScopes {
		for name, obj := range scope.elems {
			if alt := pkg.scope.Lookup(name); alt != nil {
				obj = resolve(name, obj)
				if pkg, ok := obj.(*PkgName); ok {
					check.errorf(alt, _DuplicateDecl, "%s already declared through import of %s", alt.Name(), pkg.Imported())
					check.reportAltDecl(pkg)
				} else {
					check.errorf(alt, _DuplicateDecl, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg())
					// TODO（gri）点导入的对象没有位置；reportAltDecl不会打印任何内容
					check.reportAltDecl(obj)
				}
			}
		}
	}

	// 现在我们有了所有包作用域对象和所有方法，
	// 尽可能将方法与接收器基类型名称关联。
	// 忽略具有无效接收器的方法。它们将是
	// 稍后进行类型检查，具有常规功能。
	if methods == nil {
		return // 无需操作
	}
	check.methods = make(map[*TypeName][]*Func)
	for i := range methods {
		m := &methods[i]
		// 确定接收器基类型并将m与之关联。
		ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)
		if base != nil {
			m.obj.hasPtrRecv_ = ptr
			check.methods[base] = append(check.methods[base], m.obj)
		}
	}
}

// unpackRecv解压接收器类型并返回其组件：ptr指示
// rtyp是否为指针接收器，rname是接收器类型名称，tparams是其
// 类型参数（如果有）。仅当unpackParams为
// 集时，类型参数才被解包。如果rname为nil，则接收器不可用（即，源有一个我们
// 无法轻松解决的错误）。
func (check *Checker) unpackRecv(rtyp ast.Expr, unpackParams bool) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) {
L: // 解包接收器类型
	// 这接受
	// 接收器表达式的有效性在其他地方检查。
	for {
		switch t := rtyp.(type) {
		case *ast.ParenExpr:
			rtyp = t.X
		case *ast.StarExpr:
			ptr = true
			rtyp = t.X
		default:
			break L
		}
	}

	// 解包类型参数，如果有
	switch rtyp.(type) {
	case *ast.IndexExpr, *ast.IndexListExpr:
		ix := typeparams.UnpackIndexExpr(rtyp)
		rtyp = ix.X
		if unpackParams {
			for _, arg := range ix.Indices {
				var par *ast.Ident
				switch arg := arg.(type) {
				case *ast.Ident:
					par = arg
				case *ast.BadExpr:
					// 忽略-解析器已报告错误
				case nil:
					check.invalidAST(ix.Orig, "parameterized receiver contains nil parameters")
				default:
					check.errorf(arg, _BadDecl, "receiver type parameter %s must be an identifier", arg)
				}
				if par == nil {
					par = &ast.Ident{NamePos: arg.Pos(), Name: "_"}
				}
				tparams = append(tparams, par)
			}
		}
	}

	// 解包接收方名称
	if name, _ := rtyp.(*ast.Ident); name != nil {
		rname = name
	}

	return
}

// resolveBaseTypeName返回类型的非别名基类型名称，以及
// 是否有指向它的指针。在包作用域中，基类型名称必须声明为
// 且最多只能有一个间接指针。如果不存在此类类型
// 名称，则返回的基数为零。
func (check *Checker) resolveBaseTypeName(seenPtr bool, name *ast.Ident) (ptr bool, base *TypeName) {
	// 算法：从类型表达式（可能是名称）开始，
	// 我们通过别名声明跟踪该类型，直到到达
	// 非别名类型名称。如果我们遇到指针类型或
	// 括号之外的任何东西，我们就完成了。如果我们遇到多个指针类型
	// 我们就完成了。
	ptr = seenPtr
	var seen map[*TypeName]bool
	var typ ast.Expr = name
	for {
		typ = unparen(typ)

		// 检查我们是否有指针类型
		if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil {
			// 如果我们已经看到指针，我们就完成了
			if ptr {
				return false, nil
			}
			ptr = true
			typ = unparen(pexpr.X) // 继续使用指针基类型
		}

		// 类型必须是名称
		name, _ := typ.(*ast.Ident)
		if name == nil {
			return false, nil
		}

		// 名称必须表示在当前包范围
		// （注意点导入的对象不在包范围内！）
		obj := check.pkg.scope.Lookup(name.Name)
		if obj == nil {
			return false, nil
		}

		// 对象必须是类型名。。。
		tname, _ := obj.(*TypeName)
		if tname == nil {
			return false, nil
		}

		// /。。。如果tdecl将tname定义为新类型
		if seen[tname] {
			return false, nil
		}

		// （而不是别名）
		tdecl := check.objMap[tname].tdecl // 必须存在于包范围内的对象
		if !tdecl.Assign.IsValid() {
			return ptr, tname
		}

		// 否则，继续解析
		typ = tdecl.Type
		if seen == nil {
			seen = make(map[*TypeName]bool)
		}
		seen[tname] = true
	}
}

// PackageObject类型检查所有包对象，但不检查函数体。
func (check *Checker) packageObjects() {
	// 按源代码顺序处理包对象以获得可复制的结果
	objList := make([]Object, len(check.objMap))
	i := 0
	for obj := range check.objMap {
		objList[i] = obj
		i++
	}
	sort.Sort(inSourceOrder(objList))

	// 向已检查类型添加新方法（来自之前的Checker.Files调用）
	for _, obj := range objList {
		if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {
			check.collectMethods(obj)
		}
	}

	// 我们首先处理非别名类型声明，然后是别名声明、
	// 以及其他所有内容。这似乎可以避免大多数情况，即在别名可用之前需要使用
	// 类型。
	// 在某些情况下，这可能还不够好（另见第#25838期）。
	// 在这些情况下，请检查。ident将报告一个错误（“类型别名的使用无效”）。
	var aliasList []*TypeName
	var othersList []Object // 所有不是类型的
	// 第一阶段：非别名类型声明
	for _, obj := range objList {
		if tname, _ := obj.(*TypeName); tname != nil {
			if check.objMap[tname].tdecl.Assign.IsValid() {
				aliasList = append(aliasList, tname)
			} else {
				check.objDecl(obj, nil)
			}
		} else {
			othersList = append(othersList, obj)
		}
	}
	// 第二阶段：别名类型声明
	for _, obj := range aliasList {
		check.objDecl(obj, nil)
	}
	// 第三阶段：所有其他声明
	for _, obj := range othersList {
		check.objDecl(obj, nil)
	}

	// 此时我们可能有一个非空检查。方法地图；这意味着并非所有的
	// 条目都在typeDecl末尾被删除，因为没有找到相应的接收器基
	// 类型。在这种情况下，在声明这些
	// 方法时报告了一个错误。我们现在可以安全地丢弃这张地图了。
	check.methods = nil
}

// inSourceOrder实现排序。排序界面。
type inSourceOrder []Object

func (a inSourceOrder) Len() int           { return len(a) }
func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() }
func (a inSourceOrder) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

// 未使用的导入检查未使用的导入。
func (check *Checker) unusedImports() {
	// 如果未检查函数体，则包的使用可能会丢失-不要检查
	if check.conf.IgnoreFuncBodies {
		return
	}

	// 规范：“这是非法的（…）直接导入包而不参考
	// 其任何导出标识符。若要导入一个仅用于其副作用ABCDEG

	for _, obj := range check.imports {
		if !obj.used && obj.name != "_" {
			check.errorUnusedPkg(obj)
		}
	}
}

func (check *Checker) errorUnusedPkg(obj *PkgName) {
	// 如果导入的包的名称不是最终的
	// 导入路径元素，请在错误消息中显式显示它。
	// 注意，这将处理重命名的导入和包含非常规包声明的
	// 包的导入。
	// 注意，即使在Windows上，它也使用/始终使用because Go import 
	// 路径始终使用正斜杠。
	path := obj.imported.path
	elem := path
	if i := strings.LastIndex(elem, "/"); i >= 0 {
		elem = elem[i+1:]
	}
	if obj.name == "" || obj.name == "." || obj.name == elem {
		check.softErrorf(obj, _UnusedImport, "%q imported but not used", path)
	} else {
		check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name)
	}
}

// dir真诚地尝试返回路径的目录
// 。如果路径为空，则结果为“”。
// （根据go/build包依赖性测试，我们不能导入
// path/filepath，而只使用filepath.Dir。）
func dir(path string) string {
	if i := strings.LastIndexAny(path, `/\`); i > 0 {
		return path[:i]
	}
	// i<=0 
	return "."
}
